package com.google.zxing.journeyapps.barcodescanner;

import com.google.zxing.LuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.journeyapps.barcodescanner.camera.CameraInstance;
import com.google.zxing.journeyapps.barcodescanner.camera.PreviewCallback;
import ohos.agp.utils.Rect;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.utils.PacMap;

import java.util.List;
import java.util.logging.Logger;

public class DecoderThread {
    private static final String TAG = DecoderThread.class.getSimpleName();
    private static final int zxing_decode_succeeded = 100;
    private static final int zxing_decode = 101;
    private static final int zxing_preview_failed = 102;
    private static final int zxing_decode_failed = 103;
    private static final int zxing_possible_result_points = 104;

    private CameraInstance cameraInstance;
    private EventHandler handler;
    private Decoder decoder;
    private EventHandler resultHandler;
    private Rect cropRect;
    private boolean running = false;
    private final Object LOCK = new Object();

    public DecoderThread(CameraInstance cameraInstance, Decoder decoder, EventHandler resultHandler) {
        Util.validateMainThread();

        this.cameraInstance = cameraInstance;
        this.decoder = decoder;
        this.resultHandler = resultHandler;
    }

    public Decoder getDecoder() {
        return decoder;
    }

    public void setDecoder(Decoder decoder) {
        this.decoder = decoder;
    }

    public Rect getCropRect() {
        return cropRect;
    }

    public void setCropRect(Rect cropRect) {
        this.cropRect = cropRect;
    }

    /**
     * Start decoding.
     *
     * This must be called from the UI thread.
     */
    public void start() {
        Util.validateMainThread();

        EventRunner eventRunner = EventRunner.create(TAG);
        handler = new EventHandler(eventRunner) {
            @Override
            protected void processEvent(InnerEvent event) {
                super.processEvent(event);
                if (event.eventId == zxing_decode) {
                    decode((SourceData) event.object);
                } else if (event.eventId == zxing_preview_failed) {
                    // Error already logged. Try again.
                    requestNextPreview();
                }
            }
        };

        running = true;
        requestNextPreview();
    }

    /**
     * Stop decoding.
     *
     * This must be called from the UI thread.
     */
    public void stop() {
        Util.validateMainThread();

        synchronized (LOCK) {
            running = false;
            handler.removeAllEvent();
        }
    }

    private final PreviewCallback previewCallback = new PreviewCallback() {
        @Override
        public void onPreview(SourceData sourceData) {
            // Only post if running, to prevent a warning like this:
            //   java.lang.RuntimeException: Handler (ohos.os.Handler) sending message to a Handler on a dead thread

            // synchronize to handle cases where this is called concurrently with stop()
            synchronized (LOCK) {
                if (running) {
                    // Post to our thread.
                    handler.sendEvent(InnerEvent.get(zxing_decode, sourceData));
                }
            }
        }

        @Override
        public void onPreviewError(Exception e) {
            synchronized (LOCK) {
                if (running) {
                    // Post to our thread.
                    handler.sendEvent(InnerEvent.get(zxing_preview_failed));
                }
            }
        }
    };

    private void requestNextPreview() {
        cameraInstance.requestPreview(previewCallback);
    }

    protected LuminanceSource createSource(SourceData sourceData) {
        if (this.cropRect == null) {
            return null;
        } else {
            return sourceData.createSource();
        }
    }

    private void decode(SourceData sourceData) {
        long start = System.currentTimeMillis();
        Result rawResult = null;
        sourceData.setCropRect(cropRect);
        LuminanceSource source = createSource(sourceData);

        // create PixelMap
        ImageSource imageSource = ImageSource.create(sourceData.getData(), new ImageSource.SourceOptions());
        ImageSource.DecodingOptions options = new ImageSource.DecodingOptions();
        options.rotateDegrees = 90f;
        PixelMap map = imageSource.createPixelmap(options);
        BitmapLuminanceSource bitmapSource = new BitmapLuminanceSource(map);

        if (bitmapSource != null) {
            rawResult = decoder.decode(bitmapSource);
        }

        if (rawResult != null) {
            // Don't log the barcode contents for security.
            long end = System.currentTimeMillis();
            Logger.getLogger(TAG).config("Found barcode in " + (end - start) + " ms");
            if (resultHandler != null) {
                BarcodeResult barcodeResult = new BarcodeResult(rawResult, sourceData);

                InnerEvent innerEvent = InnerEvent.get(zxing_decode_succeeded,barcodeResult);
                PacMap pacMap = new PacMap();
                innerEvent.setPacMap(pacMap);
                resultHandler.sendEvent(innerEvent);
            }
        } else {
            if (resultHandler != null) {
                InnerEvent innerEvent = InnerEvent.get(zxing_decode_failed);
                handler.sendEvent(innerEvent);
            }
        }
        if (resultHandler != null) {
            List<ResultPoint> resultPoints = BarcodeResult.transformResultPoints(decoder.getPossibleResultPoints(), sourceData);
            InnerEvent innerEvent = InnerEvent.get(zxing_possible_result_points, resultPoints);
            handler.sendEvent(innerEvent);
        }
        requestNextPreview();
    }
}
